package com.artnology.independence.reengineering;

import java.awt.Color;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.Stack;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.IConsoleManager;
import org.eclipse.ui.console.MessageConsole;
import org.eclipse.ui.console.MessageConsoleStream;
import org.eclipse.ui.progress.IProgressService;

import com.artnology.independence.Activator;
import com.artnology.independence.model.DVType;
import com.artnology.independence.views.DependencyView;

public class ReengineeringParser {

	private static final String INDEPENDENCE_CONSOLE = "Independence";

	public void execute(final Set<ITypeRoot> allTypes) {

		IWorkbench wb = PlatformUI.getWorkbench();
		IProgressService ps = wb.getProgressService();

		try {
			ps.busyCursorWhile(new IRunnableWithProgress() {
				public void run(IProgressMonitor pm) {
					try {

						pm.beginTask("Re-engineering for Independence...",
								5 * allTypes.size());

						parseTypes(allTypes, pm);

						pm.subTask("Initializing graph...");

						Display.getDefault().syncExec(new Runnable() {

							@Override
							public void run() {
								showAndUpdateView();
							}

						});

					} finally {
						pm.done();
					}

				}
			});
		} catch (InvocationTargetException e) {

			e.printStackTrace();
		} catch (InterruptedException e) {
			// can be ignored
		}

	}

	public static final void parseTypes(Set<ITypeRoot> allTypes,
			IProgressMonitor pm) {
		Set<ITypeRoot> filteredTypes = new HashSet<ITypeRoot>(allTypes);
		try {
			for (ITypeRoot myType : allTypes) {
				DVType newType = null;
				if (myType instanceof ICompilationUnit) {
					ICompilationUnit compUnit = (ICompilationUnit) myType;
					newType = new DVType(
							DVType.getClassnameFromFileName(compUnit
									.getElementName()),
							compUnit.getPackageDeclarations()[0]
									.getElementName().toString(), Activator
									.getDefault().getModel());

				} else {
					// we got class file
					IClassFile classFile = (IClassFile) myType;

					// filter out inner classes
					if (classFile.getType().getTypeQualifiedName()
							.contains("$")) {
						filteredTypes.remove(myType);
						pm.worked(5);
						continue;
					}

					newType = new DVType(classFile.getType()
							.getTypeQualifiedName(), classFile.getType()
							.getPackageFragment().getElementName(), Activator
							.getDefault().getModel());

				}
				if (pm != null) {
					pm.subTask("Parsing type " + newType.getName() + "...");
					pm.worked(1);
					if (pm.isCanceled()) {
						throw new OperationCanceledException();
					}
				}
				if (!Activator.getDefault().getModel().addType(newType)) {
					if (pm != null) {
						pm.worked(4);
					}
					filteredTypes.remove(myType);
				}

			}
		} catch (JavaModelException e1) {
			e1.printStackTrace();
		}

		// distribute the work
		int numberOfThreads = Runtime.getRuntime().availableProcessors() * 2;
		// int numberOfThreads= 1;
		int numberOfTypesPerThread = -1;
		if (filteredTypes.size() > (50 * numberOfThreads)) {
			numberOfTypesPerThread = filteredTypes.size() / numberOfThreads;
		} else {
			numberOfTypesPerThread = filteredTypes.size();
		}

		Stack<ITypeRoot> stack = new Stack<ITypeRoot>();
		stack.addAll(filteredTypes);
		Collection<ParsingThread> allThreads = new HashSet<ParsingThread>();
		int id = 1;

		while (!stack.isEmpty()) {
			Set<ITypeRoot> subset = new HashSet<ITypeRoot>();
			for (int i = 0; i < numberOfTypesPerThread && !stack.isEmpty(); i++) {
				subset.add(stack.pop());
			}
			ParsingThread pt = new ParsingThread(id, pm, subset);
			allThreads.add(pt);
			pt.start();
			id++;

		}

		for (ParsingThread parsingThread : allThreads) {
			try {
				parsingThread.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	@SuppressWarnings("unused")
	private static final Color lighterColor(Color inColor) {
		float[] hsb = Color.RGBtoHSB(inColor.getRed(), inColor.getGreen(),
				inColor.getBlue(), null);
		hsb[2] = hsb[2] * 1.1f;
		return Color.getHSBColor(hsb[0], hsb[1], hsb[2]);
	}

	public static final void writeConsoleMessage(String message) {
		MessageConsole myConsole = findConsole(INDEPENDENCE_CONSOLE);
		MessageConsoleStream out = myConsole.newMessageStream();
		out.println(message);
	}

	private static final void showAndUpdateView() {
		// try to find view
		DependencyView view = DependencyView.getView();
		if (view != null) {
			// if it's active (open) already we have to hide (close) it in order
			// to let Eclipse call the .dispose() method before
			// creating a new instance
			PlatformUI.getWorkbench().getActiveWorkbenchWindow()
					.getActivePage().hideView(view);
		}
		try {
			PlatformUI.getWorkbench().getActiveWorkbenchWindow()
					.getActivePage().showView(DependencyView.ID);
		} catch (PartInitException e) {
			e.printStackTrace();
		}

	}

	private static MessageConsole findConsole(String name) {
		ConsolePlugin plugin = ConsolePlugin.getDefault();
		IConsoleManager conMan = plugin.getConsoleManager();
		IConsole[] existing = conMan.getConsoles();
		for (int i = 0; i < existing.length; i++)
			if (name.equals(existing[i].getName()))
				return (MessageConsole) existing[i];
		// no console found, so create a new one
		MessageConsole myConsole = new MessageConsole(name, null);
		conMan.addConsoles(new IConsole[] { myConsole });
		return myConsole;
	}

}
